/*
    nspire-gamekit - a simple homebrew game framework
    Copyright (C) 2011  Daniel Tang

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <os.h>
#include "screen.h"
#include "gameloop.h"

static struct {
    unsigned int origDivisor;
    unsigned int currentVal;
} timerVals;

static void setupTimers();
static void restoreTimers();
static void waitTimer();

#define FPS_UPDATES 4 //The minimum amount of time to wait before issuing a fps update. Longer is more accurate


int gameLoopRun(game_event_funcs_t* actions, void* userdata, unsigned int fps) {
    gameLoopSetFps(fps);
    setupTimers();
    
    int returnCode = 0;
    unsigned frames;
    unsigned starttime = *(unsigned int*)0x90090000;
    unsigned difftime;
    
    /* User defined init functions */
    if (actions->init) actions->init(userdata);
    
    frames = 0;
    if (actions->update) {
        while (!(returnCode = actions->update(userdata))) {
            /* 
             * Send call user defined functions
             * Because of performance issues, we'll only
             * call the draw event.
             */

            if (actions->draw) actions->draw(userdata);
            frames++;
            
            //Maybe we'll leave this fps in after all
            
            if (actions->fps) {
                difftime = *(unsigned int*)0x90090000 - starttime;
                if ((difftime) > FPS_UPDATES) {
                    /*char buffer[100];
                    sprintf(buffer,"frames = %u, difftime = %u, eq = %f",
                        frames, difftime, 1.234f);
                    show_msgbox("Diag",buffer);*/
                    actions->fps(userdata,(float)frames/(float)difftime);
                    frames = 0;
                    starttime = *(unsigned int*)0x90090000;
                } 
            }
    
            waitTimer();
        }
    }
    if (actions->finish) actions->finish(userdata);
    restoreTimers();
    return returnCode;
}

void gameLoopSetFps(unsigned int fps) {
    fps = 1000/fps;
    timerVals.currentVal = fps;
}

/* Static methods */

static inline void setupTimers() {
    volatile unsigned *timer = (unsigned*)0x900D0000;
	volatile unsigned *divider = (unsigned*)0x900D0004;
	
	timerVals.origDivisor = *divider;
	
	*divider = 31;
	*timer = timerVals.currentVal;
}
static inline void restoreTimers() {
    volatile unsigned *timer = (unsigned*)0x900D0000;
	volatile unsigned *divider = (unsigned*)0x900D0004;
	
    *divider = timerVals.origDivisor;
	*timer = 32;
}

static inline void waitTimer() {
    volatile unsigned *timer = (unsigned*)0x900D0000;
    unsigned spare = 0;
    while (*timer > 0) { spare++; idle(); }
    *timer = timerVals.currentVal;
    return;
}